package au.edu.adelaide.mips_soft_core.board_interface_pc.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellRenderer;


import au.edu.adelaide.mips_soft_core.board_interface_pc.core.Core;
import au.edu.adelaide.mips_soft_core.board_interface_pc.core.CoreListener;
import au.edu.adelaide.mips_soft_core.board_interface_pc.core.Device;
import au.edu.adelaide.mips_soft_core.board_interface_pc.core.DeviceListener;
import au.edu.adelaide.mips_soft_core.board_interface_pc.core.Processor;
import au.edu.adelaide.mips_soft_core.board_interface_pc.core.ProcessorListener;

public class DebuggerPanel extends JPanel implements CoreListener, DeviceListener{

	ArrayList<SystemDebugger> processors = new ArrayList<SystemDebugger>();
	JLabel processorLabel = new JLabel("Processors");

	Device connectedDevice = null;

	public DebuggerPanel(){
		BoxLayout bl = new BoxLayout(this, BoxLayout.Y_AXIS);
		this.setLayout(bl);
		this.add(new JLabel("Processors"));
		Core.getCore().addCoreListener(this);
	}

	void updatePanel(){
		clear();
		if(this.connectedDevice == null){
			return;
		}
		for(int i = 0; i < this.connectedDevice.getNumberOfProcessor(); i++){
			addProcessor(this.connectedDevice.getProcessor(i));
		}
	}

	void addProcessor(Processor processor){
		SystemDebugger sd = new SystemDebugger(processor);
		this.processors.add(sd);
		this.add(sd);
	}

	void clear(){
		for(int i = 0; i < this.processors.size(); i++){
			SystemDebugger sd = this.processors.get(i);
			sd.processor.removeProcessorListener(sd);
			this.remove(sd);
		}
		this.processors.clear();
	}

	public static class SystemDebugger extends JPanel implements ProcessorListener, ActionListener{

		JButton runPause = new JButton("Run");
		JButton step = new JButton("Step");
		JLabel pcLabel = new JLabel("Program Counter:");
		JButton refresh = new JButton("Refresh");
		JButton breakPointsButton = new JButton("Break Points");
		JTextField pc = new JTextField(10);

		Processor processor;

		JLabel label;
		JTable table;
		RegisterTable rt;

		JButton treeButton;

		JPanel mainPanel;
		
		DefaultListModel<BreakPoint> breakPointsModel = new DefaultListModel<BreakPoint>();


		public SystemDebugger(Processor processor){
			super(new BorderLayout());
			{
				JPanel topPanel = new JPanel(new BorderLayout());
				treeButton = new JButton("-");
				treeButton.addActionListener(this);
				topPanel.add(treeButton, BorderLayout.WEST);
				topPanel.add(new JLabel("Processor: " + processor.processorNumber));
				this.add(topPanel, BorderLayout.NORTH);
			}
			{
				mainPanel = new JPanel(new BorderLayout());
				this.pc.setEditable(false);
				this.label  = new JLabel("Processor: "+ processor.processorNumber);
				JPanel temp = new JPanel();
				temp.add(runPause);
				temp.add(step);
				temp.add(pcLabel);
				temp.add(pc);
				temp.add(refresh);
				temp.add(breakPointsButton);
				rt = new RegisterTable(processor);
				this.table = new JTable(rt);
				
				this.table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
				this.table.setPreferredSize(new Dimension(600, 400));
				this.processor = processor;
				this.processor.addProcessorListener(this);
				this.refresh.addActionListener(this);
				this.step.addActionListener(this);
				this.runPause.addActionListener(this);
				this.breakPointsButton.addActionListener(this);
				mainPanel.add(temp, BorderLayout.NORTH);
				mainPanel.add(new JScrollPane(this.table), BorderLayout.CENTER);
				this.add(mainPanel, BorderLayout.CENTER);
			}

		}

		@Override
		public void PCChanged() {
			this.pc.setText(String.format("%08X", this.processor.pc) + " <=> " + Integer.toString(this.processor.pc));
		}

		@Override
		public void registerChanged() {
			this.rt.notifyListeners();
		}

		@Override
		public void outDated() {
			this.processor.getPCAndRegisters();
		}

		Object object = new Object();

		boolean running = false;


		public void runPause(){
			synchronized(object){
				running = !running;
				if(running){
					new Thread(){

						public void run(){
							try {
								runPause.setText("Stop");
								while(running){

									processor.stepBlocking(false);
									processor.getPCAndRegistersBlocking(true);
									for(int i = 0; i < breakPointsModel.size(); i++){
										BreakPoint bp = breakPointsModel.get(i);
										if(bp.type == BreakPoint.BREAKPOINT_PC){
											if(processor.getPC() == bp.value){
												running = false;
											}else if(bp.type == BreakPoint.BREAKPOINT_REG_ANY_VALUE){
												if(processor.registersChanged[bp.regNumber]){
													running = false;
												}
											}else if(bp.type == BreakPoint.BREAKPOINT_REG_VALUE){
												if(processor.registers[bp.regNumber] == bp.value){
													running = false;
												}
											}
										}
									}
								}
								runPause.setText("Run");
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}

					}.start();
				}

			}

		}

		@Override
		public void actionPerformed(ActionEvent event) {
			if(event.getSource() == this.runPause){
				runPause();
			}else if(event.getSource() == this.refresh){
				this.processor.getPCAndRegisters();
			}else if(event.getSource() == this.step){
				this.processor.step();
			}else if(event.getSource() == this.treeButton){
				if(this.treeButton.getText().equals("-")){
					this.treeButton.setText("+");
					this.mainPanel.setVisible(false);
				}else{
					this.treeButton.setText("-");
					this.mainPanel.setVisible(true);
				}
			}else if(event.getSource() == this.breakPointsButton){
				JPanel panel = new JPanel(new BorderLayout());
				JPanel topPanel = new JPanel();
				panel.add(topPanel, BorderLayout.NORTH);
				JButton add = new JButton("Add");
				topPanel.add(add);
				JButton remove = new JButton("Remove");
				topPanel.add(remove);
				final JList<BreakPoint> list = new JList<BreakPoint>(breakPointsModel);
				final String[] types = new String[]{"PC breakpoint", "Register Changed Value", "Register Changed to specific Value"};
				add.addActionListener(new ActionListener(){

					@Override
					public void actionPerformed(ActionEvent e) {
						int option = JOptionPane.showOptionDialog(SystemDebugger.this, "What Type of BreakPoint do you want?", "Add Breakpoint", JOptionPane.CLOSED_OPTION, JOptionPane.PLAIN_MESSAGE, null, types, types[0]);
						String name = JOptionPane.showInputDialog(SystemDebugger.this, "Breakpoint Name", "Add Breakpoint", JOptionPane.PLAIN_MESSAGE);
						if(name == null){
							return;
						}
						try{
						if(option == 0){
							String pc = JOptionPane.showInputDialog(SystemDebugger.this, "Value of PC Hex 0x", "Add Breakpoint", JOptionPane.PLAIN_MESSAGE);
							if(pc != null){
								int pcA = Integer.parseInt(pc, 16);
								BreakPoint bp = new BreakPoint(BreakPoint.BREAKPOINT_PC, name, -1, pcA);
								breakPointsModel.addElement(bp);
							}
							
						}else if(option == 1){
							String pc = JOptionPane.showInputDialog(SystemDebugger.this, "Register Number 0-31", "Add Breakpoint", JOptionPane.PLAIN_MESSAGE);
							if(pc != null){
								int regNumber = Integer.parseInt(pc);
								BreakPoint bp = new BreakPoint(BreakPoint.BREAKPOINT_REG_ANY_VALUE, name, regNumber, -1);
								breakPointsModel.addElement(bp);
							}
						}else if(option == 2){
							String pc = JOptionPane.showInputDialog(SystemDebugger.this, "Register Number 0-31", "Add Breakpoint", JOptionPane.PLAIN_MESSAGE);
							if(pc == null){
								return;
								
							}
							int regNumber = Integer.parseInt(pc);
							String regV = JOptionPane.showInputDialog(SystemDebugger.this, "Register Value Hex 0x", "Add Breakpoint", JOptionPane.PLAIN_MESSAGE);
							if(regV == null){
								return;
							}
							int regVal = (int)(Long.parseLong(regV, 16) & 0xFFFFFFFF);
							BreakPoint bp = new BreakPoint(BreakPoint.BREAKPOINT_REG_VALUE, name, regNumber, regVal);
							breakPointsModel.addElement(bp);
						}
						}catch(Exception ex){
							JOptionPane.showMessageDialog(SystemDebugger.this, ex.getMessage(), "Error",  JOptionPane.ERROR_MESSAGE);
						}
					}
					
				});
				
				remove.addActionListener(new ActionListener(){

					@Override
					public void actionPerformed(ActionEvent e) {
						if(list.getSelectedIndex() != -1){
							breakPointsModel.remove(list.getSelectedIndex());
						}
						
						
					}
					
				});
				
				panel.add(new JScrollPane(list), BorderLayout.CENTER);
				JOptionPane.showMessageDialog(this, panel, "Breakpoints", JOptionPane.PLAIN_MESSAGE);
			}
		}

		public static class BreakPoint{

			public final static int BREAKPOINT_PC = 0;
			public final static int BREAKPOINT_REG_ANY_VALUE = 1;
			public final static int BREAKPOINT_REG_VALUE = 2;

			public int regNumber;
			public int value;
			public String name;
			public int type;

			public BreakPoint(int type, String name, int regNumber, int value){
				this.type = type;
				this.name = name;
				this.regNumber = regNumber;
				this.value = value;
			}
			
			public String toString(){
				if(type == BREAKPOINT_PC){
					return name + " @ " + "PC =0x" + String.format("%08X", this.value);
				}else if(type == BREAKPOINT_REG_ANY_VALUE){
					return name + " @ " + "Reg[" + regNumber + "] = Change";
				}else if(type == BREAKPOINT_REG_VALUE){
					return name + " @ " + "Reg[" + regNumber + "] = 0x" + String.format("%08X", this.value);
				}else{
					return null;
				}
			}


		}



	}

	@Override
	public void error(String message) {
		// TODO Auto-generated method stub

	}

	@Override
	public void boardDiagnosed() {
		this.updatePanel();

	}

	@Override
	public void modeChanged() {
		// TODO Auto-generated method stub

	}

	@Override
	public void deviceListChanged() {
		// TODO Auto-generated method stub

	}

	@Override
	public void deviceConnected(Device device) {
		this.connectedDevice = device;
		this.connectedDevice.addDeviceListener(this);

	}

	@Override
	public void deviceDisconnected(Device device) {
		this.connectedDevice = null;
		device.removeListener(this);
		updatePanel();

	}



}
